#include "core/style.h"
#include "util/tstring.h"

namespace core
{
    BEGIN_ATTRIBUTE_MAP(StStyle)
        ATTRIBUTE_PARSER(STYLE_PROPERTY_BACKGROUND, ParseBackground, ATTRIBUTE_TRAIT_PAINT)
        ATTRIBUTE(STYLE_PROPERTY_BACKGROUND_COLOR, StAttributeColor, ATTRIBUTE_TRAIT_PAINT)
        ATTRIBUTE(STYLE_PROPERTY_BACKGROUND_IMAGE, StAttributeFile, ATTRIBUTE_TRAIT_PAINT)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_BACKGROUND_REPEAT, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("repeat-x", BACKGROUND_REPEAT_X)
            ATTRIBUTE_ENUM_VALUE("repeat-y", BACKGROUND_REPEAT_Y)
            ATTRIBUTE_ENUM_VALUE("repeat", BACKGROUND_REPEAT_X | BACKGROUND_REPEAT_Y)
            ATTRIBUTE_ENUM_VALUE("no-repeat", BACKGROUND_REPEAT_NULL)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_PAINT)

        ATTRIBUTE(STYLE_PROPERTY_COLOR, StAttributeColor, ATTRIBUTE_TRAIT_PAINT)
        ATTRIBUTE(STYLE_PROPERTY_LINE_HEIGHT, StAttributeInteger, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_HORIZONTAL_ALIGN, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("left", HORIZONTAL_ALIGNMENT_LEFT)
            ATTRIBUTE_ENUM_VALUE("center", HORIZONTAL_ALIGNMENT_CENTER)
            ATTRIBUTE_ENUM_VALUE("right", HORIZONTAL_ALIGNMENT_RIGHT)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_VERTICAL_ALIGN, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("top", VERTICAL_ALIGNMENT_TOP)
            ATTRIBUTE_ENUM_VALUE("middle", VERTICAL_ALIGNMENT_MIDDLE)
            ATTRIBUTE_ENUM_VALUE("bottom", VERTICAL_ALIGNMENT_BOTTOM)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_TEXT_ALIGN, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("top", VERTICAL_ALIGNMENT_TOP)
            ATTRIBUTE_ENUM_VALUE("middle", VERTICAL_ALIGNMENT_MIDDLE)
            ATTRIBUTE_ENUM_VALUE("bottom", VERTICAL_ALIGNMENT_BOTTOM)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_UNDERLINE, StAttributeBool)
            ATTRIBUTE_ENUM_VALUE("underline", true)
            ATTRIBUTE_ENUM_VALUE("none", false)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_PAINT)

        ATTRIBUTE(STYLE_PROPERTY_TEXT_INDENT, StAttributeInteger, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_PARSER(STYLE_PROPERTY_FONT, ParseFont, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE(STYLE_PROPERTY_FONT_FAMILY, StAttributeWString, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_FONT_SIZE, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_FONT_STYLE, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("normal", FONT_STYLE_NORMAL)
            ATTRIBUTE_ENUM_VALUE("italic", FONT_STYLE_ITALIC)
            ATTRIBUTE_ENUM_VALUE("bold", FONT_STYLE_BOLD)
            ATTRIBUTE_ENUM_VALUE("mix", FONT_STYLE_BOLD_ITALIC)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_PARSER(STYLE_PROPERTY_BORDER, ParseBorder, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE(STYLE_PROPERTY_BORDER_WIDTH, StAttributeInteger, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_BORDER_STYLE, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("solid", BORDER_STYLE_SOLID)
            ATTRIBUTE_ENUM_VALUE("dashed", BORDER_STYLE_DASHED)
            ATTRIBUTE_ENUM_VALUE("dotted", BORDER_STYLE_DOTTED)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_PAINT)

        ATTRIBUTE(STYLE_PROPERTY_BORDER_COLOR, StAttributeColor, ATTRIBUTE_TRAIT_PAINT)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_POSITION, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("absolute", POSITION_ABSOLUTE)
            ATTRIBUTE_ENUM_VALUE("relative", POSITION_RELATIVE)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_PARSER(STYLE_PROPERTY_MARGIN, ParseMargin, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_MARGIN_LEFT, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_MARGIN_TOP, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_MARGIN_RIGHT, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_MARGIN_BOTTOM, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_PARSER(STYLE_PROPERTY_PADDING, ParsePadding, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_PADDING_LEFT, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_PADDING_TOP, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_PADDING_RIGHT, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_PADDING_BOTTOM, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE(STYLE_PROPERTY_LEFT, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_TOP, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_RIGHT, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_BOTTOM, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE(STYLE_PROPERTY_WIDTH, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)
        ATTRIBUTE(STYLE_PROPERTY_HEIGHT, StAttributeUnit, ATTRIBUTE_TRAIT_BOTH)

        ATTRIBUTE_ENUM_BEGIN(STYLE_PROPERTY_CURSOR, StAttributeDword)
            ATTRIBUTE_ENUM_VALUE("hand", CURSOR_TYPE_HAND)
            ATTRIBUTE_ENUM_VALUE("arrow", CURSOR_TYPE_ARROW)
        ATTRIBUTE_ENUM_END(ATTRIBUTE_TRAIT_NULL)
    END_ATTRIBUTE_MAP()

    StStyle::StStyle() {}

    StStyle::StStyle(const StStyle& style)
        : StAttributeSet(style) {}

    StStyle::~StStyle() {}

    StStyle& StStyle::operator=(const StStyle& style)
    {
        if (this == &style)
        {
            return *this;
        }
        StAttributeSet::operator=(style);
        return *this;
    }

    const char * StStyle::ParseValue(const char * p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return 0;
        }

        XmlString name;
        p = XmlBase::ReadText(p, &name, true, false, ":", true, XML_DEFAULT_ENCODING);

        XmlString value;
        if (strcmp(name.c_str(), STYLE_PROPERTY_BACKGROUND) == 0 ||
            strcmp(name.c_str(), STYLE_PROPERTY_FONT) == 0 ||
            strcmp(name.c_str(), STYLE_PROPERTY_BORDER) == 0 ||
            strcmp(name.c_str(), STYLE_PROPERTY_MARGIN) == 0 ||
            strcmp(name.c_str(), STYLE_PROPERTY_PADDING) == 0)
        {
            p = XmlBase::ReadText(p, &value, false, false, ";", true, XML_DEFAULT_ENCODING);
        }
        else
        {
            p = XmlBase::ReadText(p, &value, true, false, ";", true, XML_DEFAULT_ENCODING);
        }

        unsigned result = 0;
        ParseAttribute(name.c_str(), value.c_str(), result);

        return p;
    }

    void StStyle::ParseValues(const char * p)
    {
        while (p && *p)
            p = ParseValue(p);
    }

    const char * StStyle::ParseStyle(const char * p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return 0;
        }

        p = XmlBase::SkipWhiteSpace(p, XML_DEFAULT_ENCODING);

        if (*p == '.') 
        {
            SetType(STYLE_CLASS);
            p++;
        }
        else if (*p == '#') 
        {
            SetType(STYLE_IDENTIFIER);
            p++;
        }
        else
            SetType(STYLE_TAG);

        XmlString name;
        p = XmlBase::ReadText(p++, &name, true, false, "{", true, XML_DEFAULT_ENCODING);
        if (p)
        {
            XmlString::size_type st = name.find(':');
            if (st != XmlString::npos)
            {
                char *s = new char[st];
                if (s)
                {
                    strcpy_s(s, st, name.c_str());
                    s[st] = '\0';
                }
                SetName(s);

                const char * t = name.c_str();
                st++;
                if (strcmp(t + st, "hover") == 0)
                {
                    SetState(ACTION_STATE_HOVER);
                }
                else if (strcmp(t + st, "down") == 0)
                {
                    SetState(ACTION_STATE_DOWN);
                }
            }
            else
            {
                SetName(name.c_str());
                SetState(ACTION_STATE_NORMAL);
            }

            while (p && *p)
            {
                p = ParseValue(p);
                p = XmlBase::SkipWhiteSpace(p, XML_DEFAULT_ENCODING);
                if (p && *p == '}')
                    break;
            }

            if (p && *p == '}')
                p++;
        }

        return p;
    }

    void StStyle::Copy(const StStyle& style)
    {
        for (MapType::const_iterator it = style.attributes_.begin(); it != style.attributes_.end(); ++it)
        {
            KeyAttribute key = it->first;
            MapType::iterator iter = attributes_.find(key);
            if (iter != attributes_.end())
            {
                delete iter->second;
            }

            StObject* obj = it->second->Clone();
            if (!obj)
            {
                continue;
            }

            attributes_[key] = obj;
        }
    }

    void StStyle::Inherit(const StStyle& style)
    {
        for (MapType::const_iterator it = style.attributes_.begin(); it != style.attributes_.end(); ++it)
        {
            bool inherit = false;
            KeyAttribute key = it->first;
            if (KeyAttributeEquals(StringToKeyAttribute(STYLE_PROPERTY_LINE_HEIGHT), key) ||
                KeyAttributeEquals(StringToKeyAttribute(STYLE_PROPERTY_COLOR), key) ||
                KeyAttributeEquals(StringToKeyAttribute(STYLE_PROPERTY_FONT_FAMILY), key) ||
                KeyAttributeEquals(StringToKeyAttribute(STYLE_PROPERTY_FONT_SIZE), key) ||
                KeyAttributeEquals(StringToKeyAttribute(STYLE_PROPERTY_FONT_STYLE), key) ||
                KeyAttributeEquals(StringToKeyAttribute(STYLE_PROPERTY_TEXT_ALIGN), key))
            {
                inherit = true;
            }

            if (inherit)
            {
                MapType::iterator iter = attributes_.find(key);
                if (iter != attributes_.end())
                {
                    delete iter->second;
                }

                StObject* obj = it->second->Clone();
                if (!obj)
                {
                    continue;
                }

                attributes_[it->first] = obj;
            }
        }
    }

    bool StStyle::ParseBackground(const char *p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return false;
        }

        unsigned result = 0;
        util::TString<char> tstring(p);
        tstring.Trim();

        std::string color;
        std::string::size_type pos = tstring.Tokenize(" ", 0, color);
        ParseAttribute(STYLE_PROPERTY_BACKGROUND_COLOR, color.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string file;
        pos = tstring.Tokenize(" ", pos, file);
        ParseAttribute(STYLE_PROPERTY_BACKGROUND_IMAGE, file.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string repeat;
        pos = tstring.Tokenize(" ", pos, repeat);
        ParseAttribute(STYLE_PROPERTY_BACKGROUND_REPEAT, repeat.c_str(), result);

        return true;
    }

    bool StStyle::ParseMargin(const char *p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return false;
        }

        unsigned result = 0;
        util::TString<char> tstring(p);
        tstring.Trim();

        std::string left;
        std::string::size_type pos = tstring.Tokenize(" ", 0, left);
        ParseAttribute(STYLE_PROPERTY_MARGIN_LEFT, left.c_str(), result);

        if (pos == std::string::npos)
        {
            ParseAttribute(STYLE_PROPERTY_MARGIN_TOP, left.c_str(), result);
            ParseAttribute(STYLE_PROPERTY_MARGIN_RIGHT, left.c_str(), result);
            ParseAttribute(STYLE_PROPERTY_MARGIN_BOTTOM, left.c_str(), result);
            return true;
        }

        std::string top;
        pos = tstring.Tokenize(" ", pos, top);
        ParseAttribute(STYLE_PROPERTY_MARGIN_TOP, top.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string right;
        pos = tstring.Tokenize(" ", pos, right);
        ParseAttribute(STYLE_PROPERTY_MARGIN_RIGHT, right.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string bottom;
        pos = tstring.Tokenize(" ", pos, bottom);
        ParseAttribute(STYLE_PROPERTY_MARGIN_BOTTOM, bottom.c_str(), result);

        return true;
    }

    bool StStyle::ParsePadding(const char *p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return false;
        }

        unsigned result = 0;

        util::TString<char> tstring(p);
        tstring.Trim();

        std::string left;
        std::string::size_type pos = tstring.Tokenize(" ", 0, left);
        ParseAttribute(STYLE_PROPERTY_PADDING_LEFT, left.c_str(), result);

        if (pos == std::string::npos)
        {
            ParseAttribute(STYLE_PROPERTY_PADDING_TOP, left.c_str(), result);
            ParseAttribute(STYLE_PROPERTY_PADDING_RIGHT, left.c_str(), result);
            ParseAttribute(STYLE_PROPERTY_PADDING_BOTTOM, left.c_str(), result);
            return true;
        }

        std::string top;
        pos = tstring.Tokenize(" ", pos, top);
        ParseAttribute(STYLE_PROPERTY_PADDING_TOP, top.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string right;
        pos = tstring.Tokenize(" ", pos, right);
        ParseAttribute(STYLE_PROPERTY_PADDING_RIGHT, right.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string bottom;
        pos = tstring.Tokenize(" ", pos, bottom);
        ParseAttribute(STYLE_PROPERTY_PADDING_BOTTOM, bottom.c_str(), result);

        return true;
    }

    bool StStyle::ParseFont(const char *p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return false;
        }

        unsigned result = 0;

        util::TString<char> tstring(p);
        tstring.Trim();

        std::string family;
        std::string::size_type pos = tstring.Tokenize(" ", 0, family);
        ParseAttribute(STYLE_PROPERTY_FONT_FAMILY, family.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string size;
        pos = tstring.Tokenize(" ", pos, size);
        ParseAttribute(STYLE_PROPERTY_FONT_SIZE, size.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string style;
        pos = tstring.Tokenize(" ", pos, style);
        ParseAttribute(STYLE_PROPERTY_FONT_STYLE, style.c_str(), result);

        return true;
    }

    bool StStyle::ParseBorder(const char *p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return false;
        }

        unsigned result = 0;
        util::TString<char> tstring(p);
        tstring.Trim();

        std::string width;
        std::string::size_type pos = tstring.Tokenize(" ", 0, width);
        ParseAttribute(STYLE_PROPERTY_BORDER_WIDTH, width.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string style;
        pos = tstring.Tokenize(" ", pos, style);
        ParseAttribute(STYLE_PROPERTY_BORDER_STYLE, style.c_str(), result);
        if (pos == std::string::npos)
        {
            return true;
        }

        std::string color;
        pos = tstring.Tokenize(" ", pos, color);
        ParseAttribute(STYLE_PROPERTY_BORDER_COLOR, color.c_str(), result);

        return true;
    }

    bool StStyleCache::Parse(const char *p)
    {
        if (util::String::IsEmpty<char>(p))
        {
            return false;
        }

        while (p && *p)
        {
            StStyle style;
            p = style.ParseStyle(p);
            std::string name = style.Name();
            if (StStyle::STYLE_CLASS == style.Type())
            {
                Set(name, cache_class_, style, style.State());
            }
            else if (StStyle::STYLE_IDENTIFIER == style.Type())
            {
                Set(name, cache_identifier_, style, style.State());
            }
            else if (StStyle::STYLE_TAG == style.Type())
            {
                Set(name, cache_tag_, style, style.State());
            }
        }

        return true;
    }

    bool StStyleCache::GetStyleByClass(const std::string& classname,
        StStyle* style, ActionState state) const
    {
        return Get(classname, cache_class_, style, state);
    }

    bool StStyleCache::GetStyleById(const std::string& id,
        core::StStyle* style, core::ActionState state) const
    {
        return Get(id, cache_identifier_, style, state);
    }

    bool StStyleCache::GetStyleByName(const std::string& name,
        core::StStyle* style, core::ActionState state) const
    {
        return Get(name, cache_tag_, style, state);
    }

    bool StStyleCache::Get(const std::string &name,
        const CacheMap &cache, core::StStyle* style, core::ActionState state) const
    {
        if (!style)
        {
            return false;
        }

        CacheMap::const_iterator iter = cache.find(name);
        if (iter  == cache.end())
        {
            return false;
        }

        switch (state)
        {
        case core::ACTION_STATE_NORMAL:
            *style = iter->second.style_normal_;
            return true;

        case core::ACTION_STATE_HOVER:
            *style = iter->second.style_hover_;
            return true;

        case core::ACTION_STATE_DOWN:
            *style = iter->second.style_down_;
            return true;
        }

        return false;
    }

    bool StStyleCache::Set(const std::string &name,
        CacheMap &cache, core::StStyle &style, core::ActionState state)
    {
        switch (state)
        {
        case core::ACTION_STATE_NORMAL:
            cache[name].style_normal_ = style;
            return true;

        case core::ACTION_STATE_HOVER:
            cache[name].style_hover_ = style;
            return true;

        case core::ACTION_STATE_DOWN:
            cache[name].style_down_ = style;
            return true;
        }

        return false;
    }
}